package com.xf.system.utils.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

/**
 * @author xiefengf
 */
@Slf4j
public class ExcelUtil {

    private static final String XLS = "xls";

    private static final String XLSX = "xlsx";

    private static final String ENC = "UTF-8";

    /**
     * excel下载模板工具类
     * @param tClass excel模板类
     * @param fileName 文件名
     * @param response 响应
     * @param sheetName sheet名称
     * @param <T> 返回模板
     */
    public static <T> void downloadTemplate(Class<T> tClass, String fileName, HttpServletResponse response,String sheetName){
        // 创建一个空的ArrayList，用于生成空的Excel文件
        ArrayList<T> list = new ArrayList<>();
        try {
            // 设置响应的字符编码为UTF-8
            response.setCharacterEncoding(ENC);
            // 设置响应头，指定下载的文件名（包括扩展名.xlsx），并进行URL编码以确保文件名中的特殊字符被正确处理
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "."+XLSX ,ENC));
            // 设置响应头，允许前端访问Content-Disposition响应头（某些前端框架可能需要）
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            // 指定响应内容的类型为application/vnd.ms-excel，表示这是一个Excel文件
            response.setContentType("application/vnd.ms-excel");
            // 使用EasyExcel将空列表（或实际数据）写入到响应的输出流中，生成Excel文件并发送给客户端
            EasyExcel.write(response.getOutputStream(),tClass)
                    // 设置excel初始化格式为文本
                    .registerWriteHandler(new ExcelExportWriteHandler(getClazzSize(tClass)))
                    .sheet(sheetName)
                    .doWrite(list);
        } catch (UnsupportedEncodingException e) {
            log.warn("字符串编码，异常，不支持的编码格式", e);
        } catch (IOException e) {
            log.warn("当前模板导出，IO异常", e);
        }
    }

    /***
     * excel导出工具类 - 静态导出表头
     * @param response 响应
     * @param tClass excel模板类
     * @param fileName 文件名
     * @param <T> 返回模板
     */
    public static <T> void exportExcel(HttpServletResponse response,Class<T> tClass,List<T> list,String fileName,String sheetName){
        try {
            // 设置响应的字符编码为UTF-8
            response.setCharacterEncoding(ENC);
            // 设置响应头，指定下载的文件名（包括扩展名.xlsx），并进行URL编码以确保文件名中的特殊字符被正确处理
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "."+XLSX,ENC));
            // 设置响应头，允许前端访问Content-Disposition响应头（某些前端框架可能需要）
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            // 指定响应内容的类型为application/vnd.ms-excel，表示这是一个Excel文件
            response.setContentType("application/vnd.ms-excel");
            // 使用EasyExcel将空列表（或实际数据）写入到响应的输出流中，生成Excel文件并发送给客户端
            EasyExcel.write(response.getOutputStream(),tClass)
                    // 设置excel初始化格式为文本
                    .registerWriteHandler(new ExcelExportWriteHandler(getClazzSize(tClass)))
                    // 设置单元格样式
                    .registerWriteHandler(new ExcelCellHandler())
                    // 设置排版样式
                    .registerWriteHandler(getStyleStrategy())
                    // 设置是否自动合并表头
                    .automaticMergeHead(false)
                    .sheet(sheetName)
                    .doWrite(list);
        } catch (UnsupportedEncodingException e) {
            log.warn("字符串编码，异常，不支持的编码格式", e);
        } catch (IOException e) {
            log.warn("数据导出，IO异常", e);
        }
    }

    /**
     * excel导出工具类 - 动态导出表头
     */
    public static void exportDynamicExcel(HttpServletResponse response,List<List<String>> headList, List<List<Object>> bodyList,String fileName) throws IOException {
//        // 设置响应的字符编码为UTF-8
//        response.setCharacterEncoding(ENC);
//        // 设置响应头，指定下载的文件名（包括扩展名.xlsx），并进行URL编码以确保文件名中的特殊字符被正确处理
//        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "."+XLSX,ENC));
//        // 设置响应头，允许前端访问Content-Disposition响应头（某些前端框架可能需要）
//        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
//        // 指定响应内容的类型为application/vnd.ms-excel，表示这是一个Excel文件
//        response.setContentType("application/vnd.ms-excel");
//        EasyExcel.write(response.getOutputStream()).head(headList)
//                .registerWriteHandler(new ExcelExportWriteHandler(headList.size()))
//                .registerWriteHandler(getStyleStrategy())
//                .sheet("sheet1").doWrite(bodyList);
    }



    /***
     * excel导入工具类
     * @param file 文件流
     * @param tClass excel模板类
     * @return 返回导出数据
     */
    @SneakyThrows
    public static <T> List<T> importExcel(MultipartFile file,Class<T> tClass){
        // 获取原始文件名，并提取扩展名（例如xls或xlsx）
        String originalFilename = file.getOriginalFilename();
        // 获取文件输入流，用于读取Excel内容
        InputStream inputStream = file.getInputStream();
        // 提取文件的扩展名（例如xls或xlsx）
        String extension = FilenameUtils.getExtension(originalFilename);
        // 创建一个ImportExcelListener对象，用于处理Excel文件的导入
        ImportExcelListener<T> listener = new ImportExcelListener<>();
        // 使用EasyExcel读取Excel文件的内容，并将其转换为指定的模板类T，并将结果存储在listener对象中
        EasyExcel.read(inputStream,tClass,listener).sheet().headRowNumber(0).doRead();
        // 获取Excel文件的表头字段
        String importHead = String.join(",",listener.getImportHead());
        // 获取当前类的表头字段
        String currentHead = String.join(",",getExcelHeaderNameList(tClass));
        // 校验
        if(file.isEmpty()){
            throw new RuntimeException("文件为空");
        }
        if(!XLS.equals(extension) && !XLSX.equals(extension)){
            throw new RuntimeException("文件类型不正确");
        }
        if(StringUtils.isBlank(importHead) || !importHead.equals(currentHead)){
            throw new RuntimeException("导入表头不正确");
        }
        // 获取导入的数据列表，并返回
        List<T> list = listener.getList();
        if(list.size() == 0){
            throw new RuntimeException("文件为空");
        }
        return list;
    }

    /***
     * 验证导入字段不能为空
     * @param field 字段值
     * @param fieldName 字段名
     * @param rowIndex 行号
     */
    public static void verifyStringIsNotBlank(String field,String fieldName,Integer rowIndex){
        if(StringUtils.isBlank(field)){
            if(rowIndex == null){
                throw new RuntimeException(fieldName+"存在为空的数据");
            }else{
                throw new RuntimeException("第"+rowIndex+"行，"+fieldName+"不能为空");
            }
        }
    }

    /**
     * 验证导入字段长度限制
     * @param field 字段值
     * @param fieldName 字段名
     * @param length 长度限制
     * @param rowIndex 行号
     */
    public static void verifyStringLength(String field,String fieldName,Integer length,Integer rowIndex){
        if(StringUtils.isNotBlank(field) && field.length()>length){
            if(rowIndex == null){
                throw new RuntimeException(fieldName+"限制字符长度"+length+"位");
            }else{
                throw new RuntimeException("第"+rowIndex+"行，"+fieldName+"限制字符长度"+length+"位");
            }
        }
    }

    /**
     * 获取excel表头字段集合
     * @param clazz 类型
     * @return 返回表头字段集合
     */
    private static <T> List<String> getExcelHeaderNameList(Class<T> clazz){
        // 创建一个表头字段集合
        List<String> headerList = new ArrayList<>();
        // 获取类的所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 私有属性需要设置为可访问
            field.setAccessible(true);
            // 判断是否存在ExcelProperty注解，存在则获取注解的value值
            boolean bool = field.isAnnotationPresent(ExcelProperty.class);
            if (bool) {
                // 获取注解的value值，此处假定只有一个字段名
                String name = field.getAnnotation(ExcelProperty.class).value()[0];
                // 添加到表头字段集合中
                headerList.add(name);
            }
        }
        // 返回表头字段集合
        return headerList;
    }

    /**
     * 获取类字段数量
     * @param clazz 类型
     * @return 返回字段数量
     * @param <T> 类型
     */
    private static <T> Integer getClazzSize(Class<T> clazz){
        Field[] fields = clazz.getDeclaredFields();
        return fields.length;
    }

    /**
     * 获取Excel样式策略
     * @return 返回样式策略
     */
    public static HorizontalCellStyleStrategy getStyleStrategy() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为灰色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 10);
        // 字体样式
        headWriteFont.setFontName("Frozen");
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 自动换行
        headWriteCellStyle.setWrapped(false);
        // 水平对齐方式
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直对齐方式
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontHeightInPoints((short) 10);
        // 字体样式
        contentWriteFont.setFontName("微软雅黑");
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        //自动换行
        contentWriteCellStyle.setWrapped(false);
        // 水平对齐方式
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直对齐方式
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }


}
